#include "nxrouting.h"
#include "nxrouting_pkt.h"
#include <random.h>
#include <cmu-trace.h>
#include <iostream>

int hdr_nxrouting_pkt::offset_;
static class NXroutingHeaderClass : public PacketHeaderClass {

	public:
	NXroutingHeaderClass() : PacketHeaderClass("PacketHeader/NXrouting", sizeof(hdr_nxrouting_pkt)) {
		bind_offset(&hdr_nxrouting_pkt::offset_);
	}
} class_rtProtoNXrouting_hdr;

static class NXroutingClass : public TclClass {
	public:
	NXroutingClass() : TclClass("Agent/NXrouting") {}
	TclObject* create(int argc, const char*const* argv) {
		assert(argc == 5);
		return (new NXrouting((nsaddr_t) Address::instance().str2addr(argv[4])));
	}
} class_rtProtoNXrouting;

void
NXrouting_PktTimer::expire(Event* e) {
  	agent_->send_nxrouting_pkt();
  	agent_->reset_nxrouting_pkt_timer();
}

NXrouting::NXrouting(nsaddr_t id) : Agent(PT_NXROUTING), pkt_timer_(this) {
 	bind_bool("accessible_var_", &accessible_var_);
 	ra_addr_ = id;
  	node_ = (MobileNode*)Node::get_node_by_address(id);
}

int
NXrouting::command(int argc, const char*const* argv) {
 	if (argc == 2) {
 		if (strcasecmp(argv[1], "start") == 0) {
 			pkt_timer_.resched(0.0);
 			return TCL_OK;
 		}
 		else if (strcasecmp(argv[1], "print_rtable") == 0) {
 			if (logtarget_ != 0) {
 				sprintf(logtarget_->pt_->buffer(), "P %f _%d_ Routing Table", CURRENT_TIME, ra_addr());
 				logtarget_->pt_->dump();
 				rtable_.print(logtarget_);
 			}
 			else {
 				fprintf(stdout, "%f _%d_ If you want to print this routing table "
 				"you must create a trace file in your tcl script", CURRENT_TIME, ra_addr());
 			}
 			return TCL_OK;
 		}
 	}

 	else if (argc == 3) {
 	// Obtains corresponding dmux to carry packets to upper layers
 		if (strcmp(argv[1], "port-dmux") == 0) {
 			dmux_ = (PortClassifier*)TclObject::lookup(argv[2]);
 			if (dmux_ == 0) {
 				fprintf(stderr, "%s: %s lookup of %s failed\n", __FILE__, argv[1], argv[2]);
			 return TCL_ERROR;
 			}
 			return TCL_OK;
 		}
 		// Obtains corresponding tracer
 		else if (strcmp(argv[1], "log-target") == 0 || strcmp(argv[1], "tracetarget") == 0) {
 			logtarget_ = (Trace*)TclObject::lookup(argv[2]);
 			if (logtarget_ == 0)
 				return TCL_ERROR;
			return TCL_OK;
 		}
 	}
 	// Pass the command to the base class

       else if (argc == 4) {
          if(strcmp(argv[1], "if-queue") == 0) {
                PriQueue *ifq = (PriQueue *)TclObject::lookup(argv[3]);
                int temp_ = atoi(argv[2]);
                if(temp_ == nIfaces) {
                      nIfaces++;
                  }
                assert(temp_ >= 0 && temp_ < MAX_IF);
                ifqueuelist[temp_] = ifq;
                if(ifqueuelist[temp_]) {
                      return TCL_OK;
                }else{
                      return TCL_ERROR;
                  }
          }
          if(strcmp(argv[1],"target") == 0) {
            int temp_ = atoi(argv[2]);
            if(temp_ == nIfaces) {
                  nIfaces++;
            }
            assert(temp_ >= 0 && temp_ < MAX_IF);
            targetlist[temp_] = (NsObject *)TclObject::lookup(argv[3]);
            if(targetlist[temp_]){
                  return TCL_OK;
            } else {
                  return TCL_ERROR;
            }
          }
        }

 	return Agent::command(argc, argv);
}

void
NXrouting::recv(Packet* p, Handler* h) {
	struct hdr_cmn* ch = HDR_CMN(p);
 	struct hdr_ip* ih = HDR_IP(p);

 	if (ih->saddr() == ra_addr()) {
 		// If there exists a loop, must drop the packet
 		if (ch->num_forwards() > 0) {
 			drop(p, DROP_RTR_ROUTE_LOOP);
 			return;
 		}
 		// else if this is a packet I am originating, must add IP header
 		else if (ch->num_forwards() == 0)
 			ch->size() += IP_HDR_LEN;
 	}

 	// If it is a nxrouting packet, must process it
 	if (ch->ptype() == PT_NXROUTING)
 		recv_nxrouting_pkt(p);
 	// Otherwise, must forward the packet (unless TTL has reached zero)
 	else {
 		ih->ttl_--;
 		if (ih->ttl_ == 0) {
 			drop(p, DROP_RTR_TTL);
 			return;
 		}
 		forward_data(p);
 	}
}

void
NXrouting::recv_nxrouting_pkt(Packet* p) {
       struct hdr_cmn* ch = HDR_CMN(p);
	struct hdr_ip* ih = HDR_IP(p);
	struct hdr_nxrouting_pkt* ph = HDR_NXROUTING_PKT(p);

 	// All routing messages are sent from and to port RT_PORT,
 	// so we check it.
 	assert(ih->sport() == RT_PORT);
 	assert(ih->dport() == RT_PORT);

 	/* ... processing of nxrouting packet ... */
 	int Iface;
 	if(nIfaces){
            Iface = ch->iface()-((Mac *)ifqueuelist[0]->target())->addr();
//            printf("Rtable: Node %d \n ch->iface() %d - ((Mac *)ifqueuelist[0]->target())->addr() %d\n", ra_addr_, ch->iface(), ((Mac *)ifqueuelist[0]->target())->addr());

       }else {
            Iface = -1;
        }

 	rtable_.add_entry(ih->saddr(), ih->saddr(), Iface);
// 	printf("Rtable: Node %d -> %d : next hop %d, If %d \n", ra_addr_, ih->saddr(), ih->saddr(), Iface);
 	// Release resources
 	Packet::free(p);
}

void
NXrouting::send_nxrouting_pkt() {
	Packet* p = allocpkt();
 	struct hdr_cmn* ch = HDR_CMN(p);
 	struct hdr_ip* ih = HDR_IP(p);
 	struct hdr_nxrouting_pkt* ph = HDR_NXROUTING_PKT(p);

 	ph->pkt_src() = ra_addr();
 	ph->pkt_len() = 7;
 	ph->pkt_seq_num() = seq_num_++;

 	ch->ptype() = PT_NXROUTING;
 	ch->direction() = hdr_cmn::DOWN;
 	ch->size() = IP_HDR_LEN + ph->pkt_len();
 	ch->error() = 0;
 	ch->next_hop() = IP_BROADCAST;
 	ch->addr_type() = NS_AF_INET;

 	ih->saddr() = ra_addr();
	ih->daddr() = IP_BROADCAST;
 	ih->sport() = RT_PORT;
 	ih->dport() = RT_PORT;
 	ih->ttl() = IP_DEF_TTL;

 	//Scheduler::instance().schedule(target_, p, JITTER);
 	mbcast(p);
}

void
NXrouting::reset_nxrouting_pkt_timer() {
	pkt_timer_.resched((double).5);
}

void
NXrouting::forward_data(Packet* p) {
      struct hdr_cmn* ch = HDR_CMN(p);
      struct hdr_ip* ih = HDR_IP(p);



      if (ch->direction() == hdr_cmn::UP &&
          ((u_int32_t)ih->daddr() == IP_BROADCAST || ih->daddr() == ra_addr())) {
          dmux_->recv(p, 0);
          return;
     }


     else {
         ch->direction() = hdr_cmn::DOWN;
         ch->addr_type() = NS_AF_INET;
         if ((u_int32_t)ih->daddr() == IP_BROADCAST){
               ch->next_hop() = IP_BROADCAST;
               ubcast(p, 1);
         } else {
             nsaddr_t next_hop = rtable_.lookup(ih->daddr());
             int tx_Iface = rtable_.findif(ih->daddr());
             if (next_hop == IP_BROADCAST) {
                 debug("%f: Agent %d can not forward a packet destined to %d\n",
                     CURRENT_TIME,
                     ra_addr(),
                     ih->daddr());
                 drop(p, DROP_RTR_NO_ROUTE);
                 return;
              }
            else if(tx_Iface != -1){
                 ch->next_hop() = next_hop;
                 //printf(""TIME_FORMAT"\t Routing: unicast from Node %d to Node %d via Iface %d\n", CURRENT_TIME, ra_addr_, next_hop, tx_Iface);
                 unicast(p, tx_Iface);
             }
         }
         //Scheduler::instance().schedule(target_, p, 0.0);

     }
}

void
NXrouting::mbcast(Packet * p) {
  if(nIfaces) {
    for(int i = 0; i < nIfaces; i++){
      Packet *p_cpy = p->copy();
      Scheduler::instance().schedule(targetlist[i], p_cpy, JITTER);
    }
    Packet::free(p);
  }else {
    Scheduler::instance().schedule(target_, p, JITTER);
  }
}

void
NXrouting::ubcast(Packet * p, int Iface){
  if(nIfaces) {
	/*fprintf(stderr,"ALL ubcast data type %d from interface %d from all %d\n",p->userdata()->type(), Iface, nIfaces);
	assert(Iface == 1);*/
    Scheduler::instance().schedule(targetlist[Iface], p, 0.0);
  }else{
	  //fprintf(stderr,"ubcast data type %d\n",p->userdata()->type());
      Scheduler::instance().schedule(target_, p, 0.0);
  }
}

void
NXrouting::unicast(Packet * p, int Iface){
  if(nIfaces) {
	  //fprintf(stderr,"ALL unicast data type %d from interface %d from all %d\n",p->userdata()->type(), Iface, nIfaces);
      Scheduler::instance().schedule(targetlist[Iface], p, 0.0);
    }else{
    	//fprintf(stderr,"unicast data type %d\n",p->userdata()->type());
      Scheduler::instance().schedule(target_, p, 0.0);
    }
}

int
NXrouting::getIface(Packet* p) {
//  struct hdr_cmn* ch = HDR_CMN(p);
//  if(nIfaces){
//    return ch->iface()-((Mac *)ifqueuelist[0]->target())->addr();
//  }else {
//    return -1;
//  }
  return 0;
}
